/* 
 * File:   OperacionActualizacionEstadoRepositorioLocal.cpp
 * Author: maximiliano
 * 
 * Created on 15 de junio de 2013, 10:41
 */

#include "client_OperacionActualizacionEstadoRepositorioLocal.h"
#include "client_OperacionCargaArchivo.h"
#include "client_OperacionDescargaArchivo.h"
#include "common_TokenFactory.h"

using namespace TP::Comunicacion;
using namespace TP::Operaciones;

OperacionActualizacionEstadoRepositorioLocal::OperacionActualizacionEstadoRepositorioLocal(const uint32_t &pToken, Repositorio **pRepositorioLocal, Repositorio **pRepositorioServidor, FileServiceProxy* pFileService) : Operacion(pToken) {
    _repositorioLocal = pRepositorioLocal;
    _repositorioServidor = pRepositorioServidor;
    _fileService = pFileService;
    this->setFinalizada(true);
}

OperacionActualizacionEstadoRepositorioLocal::~OperacionActualizacionEstadoRepositorioLocal() {}

void OperacionActualizacionEstadoRepositorioLocal::ejecutar(){
    
    this->setFinalizada(false);
    
    /* Se actualiza el repositorio local para que refleje los cambios que subrio
     * el repositorio por parte del servidor.  Basicamente, realiza un merge
     * entre el repositorio local y el del servidor y configura los estados
     * de los archivos en el repositorio local.
     * 
     */
    
    this->ActualizarRepositorioLocal();
    
    this->setFinalizada(true);
}

void OperacionActualizacionEstadoRepositorioLocal::procesarRespuesta (vector<string> &pRespuesta){}

void OperacionActualizacionEstadoRepositorioLocal::ActualizarRepositorioLocal(){

    this->AnalizarArchivosNuevosOModificadosDesdeCliente();
    
    this->AnalizarArchivosEliminadosDesdeCliente();
    
    /* Se debe recorrer secuencialmente los archivos del repositorio remoto en busca de
     * archivos nuevos, eliminados o modificados respecto del repositorio local 
     * y configurar el estado del respositorio
     * local a fin de que pueda ser actualizado por la operacion de sincronizacion.
     */
    
    this->AnalizarArchivosNuevosOModificadosDesdeServidor();

    /* Se verifica si se eliminaron archivos respecto del repositorio del servidor.  
     * De ser así, se actualiza el estado del archivo del repositorio.
     */
    this->analizarArchivosEliminadosDesdeServidor();
}

bool OperacionActualizacionEstadoRepositorioLocal::esArchivoNuevoDesdeServidor(const string &pNombreArchivo){

    /* Existe un archivo nuevo en el servidor cuando el archivo no figura en el repositorio 
     * local y el estado del archivo es sincronizado.
     */
    bool archivoNuevo = false;
    RepoItem* item = (*_repositorioLocal)->GetRepoItemByFilename(pNombreArchivo);
    
    if (NULL == item )
    {
        RepoItem* itemServer = (*_repositorioServidor)->GetRepoItemByFilename(pNombreArchivo);
        archivoNuevo = (itemServer->GetEstado() == ARCHIVO_SINCRONIZADO);
    }
    
    return archivoNuevo;
}

bool OperacionActualizacionEstadoRepositorioLocal::esArchivoModificadoDesdeServidor(const string &pNombreArchivo){

    /* Detecta si una archivo fue modificado respecto de la version que se encuentra en el servidor.
     * 
     */
    bool modificado = false;

    RepoItem* itemServer = (*_repositorioServidor)->GetRepoItemByFilename(pNombreArchivo);
    RepoItem* itemLocal = (*_repositorioLocal)->GetRepoItemByFilename(pNombreArchivo);
    
    if (NULL != itemServer && NULL != itemLocal)
    {
        // Realizo una primera comparacion por longitud del archivo.
        modificado = itemServer->GetCantidadHashes() != itemLocal->GetCantidadHashes();
        
        if (!modificado)
        {
            // Comparo por contenido.
            for (int32_t i = 0; i < itemServer->GetCantidadHashes() && !modificado; i++)
            {
                modificado = itemServer->GetHash(i) != itemLocal->GetHash(i);
            }
        }
    }
    return modificado;
}

void OperacionActualizacionEstadoRepositorioLocal::analizarArchivosEliminadosDesdeServidor (){

    /* Para todo item en el repositorio local, se evalua si el archivo correspondiente
     * fue eliminado.  Esto es, figura un item en el repositorio local cuando no
     * figura el correspondiente en el repositorio del servidor.
     * 
     */    
    RepoItem* item = (*_repositorioLocal)->Primero();
    bool existenArchivosBorrados = false;
    
    while (NULL != item)
    {
        /* Se verifica que el archivo local no se encuentre en estado de publicacion.
         * 
         */
        if (item->GetEstado() != ARCHIVO_A_PUBLICAR) 
        {
            RepoItem* itemServer = (*_repositorioServidor)->GetRepoItemByFilename(item->GetFilename());
            if (NULL == itemServer)
            {
                /* El archivo local fue eliminado segun datos del servidor.  Se lo configura para su
                 * eliminadion.
                 */
                item->SetEstado(ARCHIVO_A_ELIMINAR_EN_CLIENTE);
                existenArchivosBorrados = true;
            }
        }
        item = (*_repositorioLocal)->Siguiente(item);
    }
    
    if (existenArchivosBorrados)
    {
        (*_repositorioLocal)->SetVersion((*_repositorioServidor)->GetVersion() + 1);
    }
}

void OperacionActualizacionEstadoRepositorioLocal::AnalizarArchivosNuevosOModificadosDesdeServidor(){
    
    RepoItem* item = (*_repositorioServidor)->Primero();
    
    while (NULL != item)
    {
        if (this->esArchivoNuevoDesdeServidor(item->GetFilename()))
        {
            this->ConfigurarDescargaArchivoNuevo(item);
        }
        else if (this->esArchivoModificadoDesdeServidor(item->GetFilename()))
        {
            this->ConfigurarActualizacionArchivoModificadoDesdeServidor(item);
        }
        
        item = (*_repositorioServidor)->Siguiente(item);
    }
}

void OperacionActualizacionEstadoRepositorioLocal::ConfigurarDescargaArchivoNuevo (RepoItem* pItem){
    
    /* Se instancia el repoitem corresopndiente ene l repositorio local y se lo configura
     * con el estado de Archivo a descargar.
     */
    
    RepoItem* item = (*_repositorioLocal)->CrearItem();
    
    item->SetFilename(pItem->GetFilename());
    item->SetVersion(pItem->GetVersion());
    item->SetEstado(ARCHIVO_A_DESCARGAR);
    
    (*_repositorioLocal)->AgregarRepoItem(item);
}

void OperacionActualizacionEstadoRepositorioLocal::ConfigurarActualizacionArchivoModificadoDesdeServidor(RepoItem*pItem){
    
    RepoItem* item = (*_repositorioLocal)->GetRepoItemByFilename(pItem->GetFilename());
    
    if (NULL != item)
    {
        item->SetEstado(ARCHIVO_A_ACTUALIZAR);
    }
}

void OperacionActualizacionEstadoRepositorioLocal::AnalizarArchivosNuevosOModificadosDesdeCliente(){

    LNombreArchivo lista_inicial;

    if (!_fileUtils.ObtenerNombreArchivos((*_repositorioLocal)->GetPath(), lista_inicial))
    {
        /* No se puede acceder al repositorio fisico.  Se deja constancia del
         * error en el log y se finaliza la operaciond e chequeo de repositorio.
         */
        cerr << "No se puede acceder al repositorio.  Ruta inexistente (" << (*_repositorioLocal)->GetPath() << ")." << endl;

        this->setFinalizada(true);
    }

    /* Se evalua el archivo a fin de determinar si es un archivo nuevo o si 
     * el mismo fue modificado.
     */
    LNombreArchivo::iterator it;

    for(it = lista_inicial.begin(); it != lista_inicial.end(); it++)
    {       
        if (this->EsArchivoNuevoDesdeRepositorioLocal(*it))
        {
            this->ConfigurarCargaArchivoNuevo(*it);
        }
        else if (this->EsArchivoModificadoDesdeCliente(*it))
        {
            this->ConfigurarActualizacionArchivoDesdeCliente(*it);
        }
    }
}

void OperacionActualizacionEstadoRepositorioLocal::AnalizarArchivosEliminadosDesdeCliente(){
    
    /* Si el archivo fue eliminado por el usuario, existe una entrada en el repositorio local
     * sin su correspondiente archivo asociado.  Luego, se debe configurar el archivo para 
     * su eliminacion en el servidor.
     */
    
    RepoItem* item = (*_repositorioLocal)->Primero();
    
    while (NULL != item)
    {
        if (!_fileUtils.ArchivoExistente(item->GetRutaCompletaArchivo()))
        {
            if (item->GetEstado() != ARCHIVO_A_DESCARGAR)
            {
                item->SetEstado(ARCHIVO_A_ELIMINAR_EN_SERVIDOR);
            }
        }
        
        item = (*_repositorioLocal)->Siguiente (item);
    }
}

bool OperacionActualizacionEstadoRepositorioLocal::EsArchivoNuevoDesdeRepositorioLocal(const string &pNombreArchivo ){

    /* El archivo pNombreArchivo es nuevo en el directorio AU si no posee
     * su correspondiente RepoItem en el repositorio.
     * 
     */
    bool archivoNuevo = false;
    
    RepoItem* item = (*_repositorioLocal)->GetRepoItemByFilename(pNombreArchivo);
    
    archivoNuevo = (NULL == item );
    
    if (!archivoNuevo)
    {
        /* El archivo ya fue detectado y se configuro en el repositorio local.
         * Verifico si el mismo no tiene su correspondiente en el servidor.
         */
        RepoItem* itemServer = (*_repositorioServidor)->GetRepoItemByFilename(pNombreArchivo);
        
        archivoNuevo = (NULL == itemServer);
    }
    
    return archivoNuevo;
}

void OperacionActualizacionEstadoRepositorioLocal::ConfigurarCargaArchivoNuevo(const string &pNombreArchivo){
    
    
    RepoItem* item = (*_repositorioLocal)->GetRepoItemByFilename(pNombreArchivo);
    
    if (NULL == item)
    {
        item = (*_repositorioLocal)->CrearItem();
        
        item->SetFilename(pNombreArchivo);
        item->SetVersion(1);
        
        (*_repositorioLocal)->AgregarRepoItem(item);
    }
    
    item->SetEstado(ARCHIVO_A_PUBLICAR);
    
}

bool OperacionActualizacionEstadoRepositorioLocal::EsArchivoModificadoDesdeCliente(const string &pNombreArchivo ){

}

void OperacionActualizacionEstadoRepositorioLocal::ConfigurarActualizacionArchivoDesdeCliente(const string &pNombreArchivo){

}

